파드의 생애주기

개요

파드의 라이프사이클은 복합적이라 꽤나 복잡하다.
사실 엄청 어려울 개념은 아니라고 생각하는데 만든 사람들이 왜 이렇게 만들었는지 조금 궁금하다.
구체적으로는, 왜 이렇게 문서를 썼는지가 궁금하다..
단계(phase)라는 게 있고, 또 한편으로 조건(condition)이라는 것도 있다..
이 관계를 최대한 파악하면서 가보자.

전체 라이프사이클

사용자가, 혹은 워크로드를 통해 파드를 만들어야 하는 요청이 생긴다.
그러면 어느 노드에 파드를 만들 것인가가 첫번째 관건이다. -> 스케줄링
그 이후에는 해당 노드의 kubelet이 파드를 만드는 역할을 담당하며, 아래의 [[#파드 단계(phase)]]를 거치게 된다.
언젠가 종료

스케줄링

Pasted image 20240818171125.png
파드를 만드는 것에 대한 요청이 들어오는 것이 당연히 가장 먼저 선행된다.
구체적으로는 etcd에 파드에 대한 정보가 기록되고, 이를 특정 노드의 kubelet이 인지하는 과정이다.
이 과정은 전체 주기에서 한번만 발생한다.
이를 조금 더 세분화하면 이런 과정을 거친다.

과정을 이렇게 세분화하는 이유는 다음의 상황이 존재하기 때문이다.
가령 어떤 노드가 모종의 이유로 파괴되거나 연결이 끊긴다고 생각해보자.

파드의 회복

통상적으로 파드가 회복될 수 없는 상태라면 쿠버네티스는 새로운 파드를 만들어버리는 것을 선택한다.
이를 탐지하고 관리하는 작업은 kube-controller-manager가 담당한다.
(새로운 파드를 어디에 할당할지는 또다시 kube-scheduler의 일이 될 것이다.)
반대로 말하면 이 경우 동일한 UID를 가진 파드를 유지할 수는 없다는 것이다.
그래도 이름은 똑같이 할 수 있다!
이를 주의 깊게 여겨야 하는 이유는 스토리지 문제에 있다.
새로 파드가 만들어진다는 것은 이전 파드가 사용하던 볼륨이 유지되지 않는다는 것이다.
따라서 이전 파드에 중요한 정보가 휘발되지 않도록 하는 대책이 요구된다.

파드 단계(phase)

Pasted image 20240818170338.png
바인딩이 된 이후의 파드는 다음의 단계를 가진다.
이 단계는 매우 단순하고 추상 요약된 상태에 불과하다는 것을 유의해야 한다.
실제로는 각 단계 안에서 다양한 상태를 가질 수 있다.
대신 이 단계로 정의된 상태들은 순서가 불변하고 보장된다.
Running이었던 놈이 Pending으로 돌아가거나 하는 일은 발생하지 않는다는 것이다.

Terminating

참고로 파드가 종료되는 중에는 Terminating이라는 상태가 출력된다.
그러나 공식 문서에서는 이것을 페이즈로 치지 않는다.
기본적으로 파드는 안전한 종료 텀으로 30초 동안 이 상태를 유지한다.
이를 깨고 싶다면 종료 조건을 양식에 작성하거나, --force 옵션을 주도록 한다.
자세한 내용은 아래 [[#파드의 종료]]를 참고한다.

컨테이너 상태

파드의 상태와 컨테이너의 상태는 별개이다.
컨테이너의 상태는 파드의 Running 단계에서 이뤄지는 상태라고 보면 될 듯하다.
kubeletContainer Runtime을 통해 파드를 만드는 과정 속에서, 컨테이너는 다음의 세 가지 상태를 가진다.

container lifecycle hooks를 통해 컨테이너 상태에 따른 각종 작업을 진행하거나 디버깅을 할 수 있다.
위의 postStart, preStop이 이 내용에 해당한다.

파드 상태(condition)

Pasted image 20240815225457.png
아무튼 파드의 현재 상태를 나타내는 또 하나의 지표라고 보면 된다.
마찬가지로 describe를 통해 관측할 수 있다.
다음의 상태가 존재한다.

위의 값들이 True, False, Unknown의 상태로 나타날 수 있다.
Pasted image 20240815230345.png
무언가 실패하는 상태일 때는 yaml 파일로 현재 상태를 뽑아서 message라는 값을 통해 마지막 상태의 이유를 파악할 수도 있으니 참고.
어차피 logs나 describe로 확인할 수 있어서 이렇게 볼 일은 거의 없을 것이다.

Pod Readiness Gate

이건 로드밸런서와 관련된 상태이다.

spec:
  readinessGates:
    - conditionType: "www.example.com/feature-1"

이런 식으로 파드 스펙에 readinessGates라는 필드를 작성하면 파드 상태에 해당 타입이 추가된다.

이걸 언제 사용하는가?[1]
우리는 컨테이너의 상태를 체크하기 위해 레디네스 프로브를 사용하지만, 클러스터 외부에서 이를 활용하는 것이 녹록치 않은 경우가 종종 있다.
가령 파드들을 여러 개 새로 띄우거나 삭제하는 업데이트 과정이 있다고 쳐보자.
클러스터 내부에서는 이것이 빠르게 반영이 되어도 AWS LB에서 타겟 그룹을 업데이트하는 속도는 그보다 느리다.
외부 로드 밸런서는 헬스체크 상 해당 백엔드가 괜찮다고 판단하고 있는 시점에 사실 파드는 준비가 되지 않았다던가, 이런 짧은 간극이 발생할 수 있으며, 이는 서비스 장애로 이어질 가능성이 높다는 것이다.
그래서 세팅을 하는 것이 바로 레디네스 게이트이다.

여기에서 다루지는 않겠으나, 이 설정을 커스텀 해주면 이 값은 외부 로드밸런서의 헬스체크가 성공했을 때 True가 된다.
그리고 이게 세팅되면 위의 Ready라는 상태는 레디네스(Readiness)까지 충족돼야 True가 된다.
그래서 외부의 로드 밸런서가 트래픽을 정말 전달할 수 있는 상태일 때만 파드에 트래픽이 실제로 흘러갈 수 있도록 동기화를 해줄 수 있는 것이다.
이 파드가 클러스터 외부 환경에서까지 구동될 준비가 되었다를 나타내는 지표라고 보면 될 것 같다.

E-레디네스 프로브와 레디네스 게이트
전자에 대해서는 컨테이너 프로브쪽에서 다루는 것으로 하고 후자를 조금 더 본다.
Pasted image 20240815235256.png
이런 식으로 스펙에 명시를 하면, 파드의 상태의 키로 들어가게 된다.
Pasted image 20240815233225.png
그래서 describe로 또 볼 수 있게 된다.
기본적으로는 False로 설정된다고 하나 내 경우에는 그렇지 않았다.

파드 네트워크 레디네스

참고로 이전 개발 과정 상에서는 PodHasNetwork라고 불렸다고 한다.

파드가 노드에 스케줄된 후 파드는 kubelet에 의해 승인되며 필요한 스토리지 볼륨이 마운트 된다.
이 단계가 끝난 후에 kubelet은 파드의 네트워크와 런타임 샌드박스 설정을 위해서 Container Runtime과 협업한다.
만약 PodReadyToStartContainersCondition feature gate가 활성화됐다면, 이 컨디션은 파드의 컨디션으로 자동 추가된다(참고로 Kubernetes v1.31 - Elli부터는 기본이 활성화).

다음의 경우에 kubeletPodReadyToStartContainers 컨디션을 거짓으로 만든다.

당연히 이 경우가 아니라면 이 컨디션은 참이 된다.
이 값이 참이어야만 kubelet은 컨테이너 이미지를 받고, 컨테이너를 만드는 행위를 하게 된다.
init container가 있을 때와 없을 때 상황이 조금 다르다.
있다면 kubelet은 위 과정을 완료한 이후 초기화 컨테이너까지 완료됐는지 확인한 후에야 Initialzed 조건을 참으로 바꾼다.
없다면 Initialized 조건은 샌드박스 생성, 네트워크 설정 이전에 참으로 설정된다.

파드의 종료

할 일을 마쳤거나, 종료 시그널이 들어온 파드에서는 어떤 일이 발생할까?
파드는 클러스터에서 돌아가는 프로세스이기에 필요 없어졌다고 무작정 KILL 시그널을 날리기보다는 안정적으로 종료되는 것이 바람직하다.
가령 데이터를 안전하게, 아니면 요청을 완전히 수행하고 종료되는 것이 좋다는 것이다.
그래서 종료에 조금 특별한 기능들이 들어가 있는데, 바로 우아한 종료(graceful shutdown)를 지원하는 것이다.
파드는 일정 기간(기본 30초) 동안 안정적인 종료 기간을 두고, 그때까지 종료가 완료되지 않을 경우 강제 종료가 진행된다.

gracful shutdown

이 기간을 유예 기간이라고도 부른다.
기본적으로 우아한 종료 시기에 kubeletContainer Runtime에 파드 속 컨테이너를 중지하도록 요청을 날린다.
그러면 컨테이너의 메인 프로세스에 TERM 시그널이 날아가게 된다.
이건 프로세스에게 선택권을 넘겨주는 것이기에 종료는 비동기적이고, 정말 종료된다는 보장은 없다.

참고로 만약 이미지에 STOPSIGNAL이 있다면 이것을 TERM 대신 날리게 된다.
또 참고로 이 시기에 kubelet 등의 관리 서비스가 다시 시작되면, 클러스터에서는 전체 원래 유예 기간을 포함해 처음부터 다시 시도한다.

force shutdown

그러다 종료 시기가 지나면, KILL 시그널이 날아가게 되며 파드는 kube-apiserver에서 지워진다.

참고


  1. https://velog.io/@xgro/Pod-Readiness-Gate ↩︎